package cn.maihe.elg.operation.centers.epoint.sanming.channel;

import cn.maihe.elg.operation.centers.epoint.sanming.annotation.Decryption;
import cn.maihe.elg.operation.centers.epoint.sanming.auth.EpointAuthorization;
import cn.maihe.elg.operation.centers.epoint.sanming.auth.EpointEncryption;
import cn.maihe.elg.operation.centers.epoint.sanming.config.EpointAuthConfig;
import cn.maihe.elg.operation.centers.epoint.sanming.dto.EpointBaseReqDTO;
import cn.maihe.elg.operation.centers.epoint.sanming.dto.EpointBaseRespDTO;
import cn.maihe.elg.operation.centers.epoint.sanming.enums.EpointChannelMethod;
import cn.maihe.elg.operation.common.TradeContext;
import cn.maihe.elg.operation.exception.CenterException;
import cn.maihe.elg.operation.model.enums.InnerRespCode;
import cn.maihe.elg.operation.utils.ElgLogger;
import cn.maihe.elg.operation.utils.HttpsClientRequestFactory;
import cn.maihe.elg.operation.utils.ValidateUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.net.SocketTimeoutException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Map;

/**
 * @Description  请求南平新点中心基类服务
 * @Author WJH
 * @Date 2021/04/20
 */
@Service
@Slf4j
public abstract class EpointBaseChannelService<REQ extends EpointBaseReqDTO, RESP extends EpointBaseRespDTO> {

    @Resource
    private EpointAuthorization epointAuthorization;
    @Resource
    private EpointEncryption epointEncryption;
    @Resource
    protected EpointAuthConfig epointAuthConfig;
    @Resource
//    private RestTemplate restTemplate;
    private RestTemplate restTemplate = new RestTemplate(new HttpsClientRequestFactory());

    private final ObjectMapper objectMapper = new ObjectMapper();
    protected final Class<RESP> respClass;


    public EpointBaseChannelService() {
        respClass = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    /**
     * 设置公共字段：method&timestamp
     */
    protected REQ buildReq(REQ req) {
        req.setAppkey(this.epointAuthConfig.getAppKey());
        req.setTimestamp(timestamp());
        return req;
    }

    /**
     * 实际请求url
     */
    private String buildReqUrl() {
        String url = this.epointAuthConfig.getUrl().trim();
        if (url.endsWith("/")) {
            return url + methodEnum().getMethod();
        }
        return url + "/" + methodEnum().getMethod();
    }

    public RESP doRequest(REQ req) {
        return this.doRequest(req, null);
    }

    /**
     * @param req     参与签名验签数据
     * @param attrMap 不参与签名验签附加属性
     * @return
     */
    public RESP doRequest(REQ req, Map<String, Object> attrMap) {

        //构建上下文
        TradeContext context = new TradeContext(req, attrMap);
        try {
            //接口公共参数设置
            req = buildReq(req);

            //请求参数校验
            verifyParam(context);
        } catch (Exception e) {
            CenterException ce = buildCException(InnerRespCode.FAILURE_PARAM, e, "参数错误:");
            ElgLogger.error(log, l -> l.error("traceId={} => [{}]服务 参数校验失败：{}", context.getTraceId(), methodEnum().getDesc(), ce.getMessage()));
            throw ce;
        }

        //加密字段
        try {
            req = this.encrypt(req);
        } catch (Exception e) {
            CenterException ce = buildCException(InnerRespCode.FAILURE_PARAM, e, "加密失败:");
            ElgLogger.error(log, l -> l.error("traceId={} => [{}]服务 参数加密失败：{}", context.getTraceId(), methodEnum().getDesc(), ce.getMessage()));
            throw ce;
        }

        try {
            //签名
            String signature = epointAuthorization.signature(req, null);
            req.setSign(signature);
        } catch (Exception e) {
            CenterException ce = buildCException(InnerRespCode.FAILURE, e, "签名异常:");
            ElgLogger.error(log, l -> l.error("traceId={} => [{}]服务 签名异常：{}", context.getTraceId(), methodEnum().getDesc(), ce.getMessage()));
            throw ce;
        }

        //请求渠道服务
        String reqUrl = this.buildReqUrl();
        try {
            REQ finalReq = req;
            ElgLogger.info(log, l -> l.info("traceId={} => [{}]服务 请求报文：{}", context.getTraceId(), methodEnum().getDesc(), this.toJsonString(finalReq)));
            ElgLogger.info(log, l -> l.info("traceId={} => [{}]服务 请求地址：{}", context.getTraceId(), methodEnum().getDesc(), reqUrl));
            ResponseEntity<RESP> responseEntity = restTemplate.postForEntity(reqUrl, req, respClass);
            if (responseEntity != null && responseEntity.getStatusCode().is2xxSuccessful()) {
                //正常响应业务处理
                RESP resp = responseEntity.getBody();
                ElgLogger.info(log, l -> l.info("traceId={} <= [{}]服务 响应报文：{}", context.getTraceId(), methodEnum().getDesc(), this.toJsonString(resp)));
                return resp;
            }
            throw new RuntimeException("响应报文为空或http状态非成功");
        } catch (Exception e) {
            CenterException ce = buildCException(InnerRespCode.FAILURE, e, "请求响应异常:");
            ElgLogger.error(log, l -> l.error("traceId={} <= [{}]服务 请求响应异常：{}", context.getTraceId(), methodEnum().getDesc(), ce.getMessage()));
            throw ce;
        }


    }


    /**
     * 敏感字段加密处理
     *
     * @param reqDTO
     * @return
     */
    private REQ encrypt(REQ reqDTO) {
        Field[] fields = reqDTO.getClass().getDeclaredFields();
        Arrays.stream(fields).filter(f -> f.getAnnotation(Decryption.class) != null).forEach(f -> {
            f.setAccessible(true);
            try {
                String origValue = (String) f.get(reqDTO);
                if (StringUtils.isNotBlank(origValue)) {
                    f.set(reqDTO, this.epointEncryption.encrypt(origValue));
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException("[" + f.getName() + "]字段加密异常:" + e.getMessage(), e);
            }
            f.setAccessible(false);
        });
        return reqDTO;
    }

    protected CenterException buildCException(InnerRespCode innerRespCode, Exception exception, String message) {
        CenterException ce;
        if (exception instanceof SocketTimeoutException) {
            ce = CenterException.timeout("超时:" + message.concat(exception.getMessage()),exception);
        } else if (exception instanceof CenterException) {
            ce = (CenterException) exception;
        } else {
            if (exception != null) {
                ce = CenterException.exception(message.concat(exception.getMessage()),exception);
            } else {
                ce = CenterException.of(innerRespCode, message, exception);
            }
        }
        return ce;
    }

    private void verifyParam(TradeContext<REQ, RESP> context) {
        ElgLogger.debug(log, l -> l.debug("traceId={} => [{}]服务 请求参数：{}", context.getTraceId(), methodEnum().getDesc(), toJsonString(context.getReq())));
        if (StringUtils.isBlank(methodEnum().getMethod())) {
            throw CenterException.of(InnerRespCode.FAILURE_PARAM,"method参数不能为空",null);
        }
        try {
            ValidateUtil.validate(context.getReq());
        } catch (Exception e) {
            throw CenterException.of(InnerRespCode.FAILURE_PARAM,"请求参数错误",e);
        }
    }

    /**
     * 渠道方法名
     *
     * @return
     */
    protected abstract EpointChannelMethod methodEnum();


    private String timestamp() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }

    private String toJsonString(Object body) {
        try {
            return objectMapper.writeValueAsString(body);
        } catch (JsonProcessingException e) {
            throw CenterException.exception("转换json字符串异常" ,e);
        }
    }


}
